﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Mime;
using System.IO;

namespace NHINDirect
{
    public class MimeEntity
    {
        protected HeaderCollection m_headers;
        protected Body m_body;
        ContentType m_contentType;  // strongly typed, used internally for parsing

        public MimeEntity()
        {
        }
        
        public bool HasHeaders
        {
            get
            {
                return (this.m_headers != null && this.m_headers.Count > 0);
            }
        }

        public HeaderCollection Headers
        {
            get
            {
                if (this.m_headers == null)
                {
                    this.m_headers = new HeaderCollection();
                }

                return this.m_headers;
            }
            set
            {
                this.m_headers = value;
            }
        }

        public Header ContentType
        {
            get
            {
                return this.Headers[Protocol.ContentTypeHeader];
            }
            set
            {
                this.Headers[Protocol.ContentTypeHeader] = value;
                this.m_contentType = null;
            }
        }

        public Header ContentDisposition
        {
            get
            {
                return this.Headers[Protocol.ContentDispositionHeader];
            }
            set
            {
                this.Headers[Protocol.ContentDispositionHeader] = value;
            }
        }

        public Header ContentTransferEncoding
        {
            get
            {
                return this.Headers[Protocol.ContentTransferEncodingHeader];
            }
            set
            {
                this.Headers[Protocol.ContentTransferEncodingHeader] = value;
            }
        }

        public ContentType ParsedContentType
        {
            get
            {
                if (this.m_contentType == null)
                {
                    Header contentTypeHeader = this.ContentType;
                    if (contentTypeHeader != null)
                    {
                        this.m_contentType = new ContentType(contentTypeHeader.Value);
                    }
                }

                return this.m_contentType;
            }
        }

        public bool IsMultiPart
        {
            get
            {
                Header header = this.ContentType;
                if (header == null)
                {
                    return false;
                }
                return Protocol.Contains(header.Value, Protocol.MediaType_Multipart);
            }
        }

        public bool IsEncrypted
        {
            get
            {
                return Protocol.IsEncryptedContent(this.ParsedContentType);
            }
        }

        public bool IsSigned
        {
            get
            {
                return Protocol.IsSignedContent(this.ParsedContentType);
            }
        }

        public Body Body
        {
            get
            {
                return this.m_body;
            }
            set
            {
                this.m_body = value;
            }
        }

        public bool HasBody
        {
            get
            {
                return (this.m_body != null);
            }
        }
        
        public void ApplyBody(MimeEntity entity)
        {
            if (entity == null)
            {
                throw new ArgumentNullException();
            }

            this.Headers.AddUpdate(entity.Headers);
            this.Body = entity.Body;            
        }
        
        public void ApplyBody(MultipartEntity multipartEntity)
        {
            this.SetParts(multipartEntity);
        }
        
        /// <summary>
        /// Skips/ignores Prologue and the Epilogue parts...
        /// </summary>
        /// <returns></returns>
        public IEnumerable<MimeEntity> GetParts()
        {
            if (!this.IsMultiPart)
            {
                throw new InvalidOperationException();
            }
            
            foreach(EntityPart part in this.GetAllParts())
            {
                if (part.Type == EntityPartType.SubPart)
                {
                    yield return EntityParser.Read<MimeEntity>(part.SourceText);   
                }
            }
        }
        
        public IEnumerable<EntityPart> GetAllParts()
        {
            return EntityParser.ReadBodyParts(this.m_body.SourceText, this.ParsedContentType.Boundary);
        }
        
        public void SetParts(MultipartEntity entities)
        {
            if (entities == null)
            {
                throw new ArgumentException();
            }
            
            this.SetParts(entities, entities.ContentType.ToString());
        }
        
        public void SetParts(IEnumerable<MimeEntity> entities, string contentType)
        {
            if (entities == null)
            {
                throw new ArgumentException();
            }
            
            if (string.IsNullOrEmpty(contentType))
            {
                contentType = Protocol.MultiPartType_Mixed;
            }
            this.ContentType = new Header(Protocol.ContentTypeHeader, contentType);
            this.Body = new Body(EntitySerializer.Default.Serialize(entities, this.ParsedContentType.Boundary));
        }
                
        public void VerifyMediaType(string expectedType)
        {
            ContentType parsedType = this.ParsedContentType;
            if (parsedType == null || !Protocol.Equals(parsedType.MediaType, expectedType))
            {
                throw new ProtocolException(ProtocolError.ContentTypeMismatch);
            }
        }

        public void VerifyMediaType(params string[] expectedTypes)
        {
            ContentType parsedType = this.ParsedContentType;
            int matchCount = 0;
            if (parsedType != null)
            {
                for (int i = 0; i < expectedTypes.Length; ++i)
                {
                    if (Protocol.Equals(parsedType.MediaType, expectedTypes[i]))
                    {
                        matchCount++;
                    }
                }
            }
            if (matchCount == 0)
            {
                throw new ProtocolException(ProtocolError.ContentTypeMismatch);
            }
        }

        public void VerifyContentTypeParameter(string key, string expectedValue)
        {
            ContentType parsedType = this.ParsedContentType;
            string value = parsedType.Parameters[key];
            if (parsedType == null || !Protocol.Equals(value, expectedValue))
            {
                throw new ProtocolException(ProtocolError.ContentTypeMismatch);
            }
        }
        
        public void VerifyTransferEncoding(string expectedEncoding)
        {
            Header transferEncodingHeader = this.ContentTransferEncoding;
            if (transferEncodingHeader == null || !transferEncodingHeader.Value.Equals(expectedEncoding, StringComparison.OrdinalIgnoreCase))
            {
                throw new ProtocolException(ProtocolError.TransferEncodingMismatch);
            }
        }

        public override string ToString()
        {
            return EntitySerializer.Default.Serialize(this);
        }
        
        public virtual MimeEntityCollection ToParts()
        {
            if (!this.IsMultiPart)
            {
                throw new InvalidOperationException();
            }
            
            return new MimeEntityCollection(this.ContentType.Value, this.GetParts());
        }
    }
}
